package net.jxta.myjxta.presence;

import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.StringMessageElement;
import net.jxta.myjxta.MyJXTA;
import net.jxta.myjxta.View;
import net.jxta.myjxta.dialog.DialogMessage;
import net.jxta.myjxta.dialog.commands.PingCommand;
import net.jxta.myjxta.dialog.util.RemoteCommandInvoker;
import net.jxta.myjxta.ui.MyJXTAView;
import net.jxta.myjxta.ui.SingleGroupNavigationPanel;
import net.jxta.myjxta.util.Group;
import net.jxta.myjxta.util.Peer;
import net.jxta.myjxta.util.objectmodel.PeerNode;
import net.jxta.peergroup.PeerGroup;
import net.jxta.pipe.PipeID;
import net.jxta.protocol.PipeAdvertisement;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

/**
 * right now this is only a helper package, is NOT the presence implementation you are looking for ;-) !
 */
public final class PresenceController  {

    private static final HashMap<Group, PeerStatus> groupStatus = new HashMap<Group, PeerStatus>();
    private static final HashMap<Object, PresenceController> controllers = new HashMap<Object, PresenceController>();
    private final Object m_communityKey;

    private final Vector<PeerEntry> m_rows = new Vector<PeerEntry>();
    public static PeerStatus defaultStatus = PeerStatus.getOnlineState("not set");

    public static PresenceController getController(PeerGroup group) {
        return getController(group.getPeerGroupID().toString());   
    }
    public static PresenceController getController(String communityIdentifier) {
        PresenceController cont = controllers.get(communityIdentifier);
        if (cont == null) {
            cont = new PresenceController(communityIdentifier);
            controllers.put(cont.getCommunityKey(), cont);
        }
        return cont;
    }

    private Object getCommunityKey() {
		return m_communityKey;
	}

	private PresenceController(String key) {
        m_communityKey = key;
    }

    private synchronized Vector<PeerEntry> getAllContactsUnsorted() {
    	
        return m_rows;
    }
    public synchronized PeerEntry[] getAllContacts(){
        Vector<PeerEntry> tmp = getAllContactsUnsorted();
        PeerEntry[] result=new PeerEntry[tmp.size()];
        int i=0;
        for (PeerEntry entry : tmp) {
            result[i]=entry;
            i++;
        }
        return result;
    }

    private static PeerEntry isNameKnown(Group p_group, String p_name, PipeID p_contactPipe) {


        for (PeerEntry peerEntry : getController(p_group.getId()).getAllContacts()) {
            Peer peer = peerEntry.m_peer;
            if (peer.getName().equals(p_name)) {
                if (p_contactPipe == null || peer.getPipeAdvertisement().getID().equals(p_contactPipe)) {
                    //no pipe id specified? --> use only the name, otherwise try to find the correct pipeid
                    return peerEntry;
                }
            }
        }
        return null;
    }

    public static boolean updateLastSeen(Group group, String originatorName, PipeID originatorPipeID, PeerStatus senderState) {
        PeerEntry peerEntry = isNameKnown(group, originatorName, originatorPipeID);
        if (peerEntry != null) {
            peerEntry.updateLastSeen(senderState);
            return true;
        } else {
            return false;
        }
    }

    public static void pingAllNodesInGroup(Group group) {
        View view = MyJXTA.getTheInstance().getView();
        if (view instanceof MyJXTAView) {
            SingleGroupNavigationPanel singleGroupPanel = ((MyJXTAView) view).getNavigationForGroup(group);
            if (singleGroupPanel != null) {
                singleGroupPanel.pingAllNodes();
            }
        }
    }

    public static PeerStatus getOwnPeerState(Group group) {
        PeerStatus ownState = groupStatus.get(group);
        if (ownState == null) {
            ownState = defaultStatus;
        }
        return ownState;
    }

    public static String getOnlineStatusState(PipeAdvertisement requestor, Group group) {
        return getOwnPeerState(group).getState();
    }

    public static PeerStatus setOwnPeerStatus(Group g, PeerStatus newStatus) {
        PeerStatus oldStatus = getOwnPeerState(g);
        if (g == null) {
            defaultStatus = newStatus;
        } else {
            groupStatus.put(g, newStatus);
        }
        return oldStatus;
    }

    final ArrayList<PresenseListener> listeners=new ArrayList<PresenseListener>();

    public void addEntry(PeerEntry p_peerEntry,MyJXTA pingHelper) {
        m_rows.add(p_peerEntry);
        for (PresenseListener listener : listeners) {
            listener.entryAdded(p_peerEntry);
        }

        PresenceController.challengePeerNode(pingHelper,p_peerEntry.getNode().getParent().getGroup(),p_peerEntry.getNode());
    }


    public void registerListener(PresenseListener listener) {
        listeners.add(listener);    
    }

    public static void challengePeerNode(final MyJXTA control, final Group g, final PeerNode pn) {
        final Peer p = pn != null ? pn.getPeer() : null;
        if (p == null)
            throw new IllegalStateException("peer node doesnt have a peer object!");


        final PipeAdvertisement pipeAdvertisement = p.getPipeAdvertisement();
        Thread tmp=new Thread("challenge "+pn.getPeer().getName()){

            public void run() {
                challengeSynchron(g, pipeAdvertisement, control, pn);
            }
        };
        tmp.setDaemon(true);
        tmp.start();

    }

    private static void challengeSynchron(Group g, PipeAdvertisement p_pipeAdvertisement, MyJXTA control, PeerNode pn) {
        RemoteCommandInvoker cf = new RemoteCommandInvoker(g, p_pipeAdvertisement,
                new PingCommand(), control);


        pn.setInfo(PeerNode.CONNECTION_ONGOING, "true", true);
        Map<String, MessageElement> messages = new HashMap<String, MessageElement>();

        messages.put(PingCommand.REQUESTOR_PIPE, new StringMessageElement(PingCommand.REQUESTOR_PIPE, g.getOwnPeersCommandId().toString(), null));
        messages.put(PingCommand.REQUESTOR_STATE, new StringMessageElement(PingCommand.REQUESTOR_STATE, getOnlineStatusState(p_pipeAdvertisement, g), null));
        messages.put(PingCommand.REQUESTOR_STATE_MESSAGE, new StringMessageElement(PingCommand.REQUESTOR_STATE_MESSAGE, getOwnPeerState(g).getStatusMessage(), null));
        int INTERVAL = 250;
        int MAX = INTERVAL * 4 * 20;
        cf.invoke(
                MAX, messages);

        DialogMessage r = cf.getResponse();

        String res = r != null ? r.getCommand() : null;

        if (res != null) {
            MessageElement oppositeSideHasDiscoverdOurPeer = r.getMessageElement(PingCommand.REQUESTOR_DISCOVERED);
            MessageElement oppositeSideStatus = r.getMessageElement(PingCommand.RESPONDER_STATE);
            MessageElement oppositeSideStatusMessage = r.getMessageElement(PingCommand.RESPONDER_STATE_MESSAGE);


            Boolean oppositePeerHasDiscovered = null;
            if (oppositeSideHasDiscoverdOurPeer != null) {
                if ("true".equalsIgnoreCase(oppositeSideHasDiscoverdOurPeer.toString()))
                    oppositePeerHasDiscovered = Boolean.TRUE;
                else
                    oppositePeerHasDiscovered = Boolean.FALSE;
            }

            PeerStatus opposidePresenceStatus = null;
            if (oppositeSideStatus != null) {
                String message = oppositeSideStatusMessage != null ? oppositeSideStatusMessage.toString() : null;
                opposidePresenceStatus = PeerStatus.getPeerStatus(oppositeSideStatus.toString(), message);
            }

            //suboptimal... we should be able to transfer more than one parameter to the node

            //no notify for this one (next one will notify listeners)
            pn.setInfo(PeerNode.STATUS_REVERSE_DISCOVERED, oppositePeerHasDiscovered, false);
            pn.setInfo(PeerNode.STATUS_REACHABLE, Boolean.TRUE, false);
            pn.setInfo(PeerNode.STATUS_PRESENCE, opposidePresenceStatus, true);


        } else {
            pn.setInfo(PeerNode.STATUS_REACHABLE, Boolean.FALSE, true);
        }
        pn.setInfo(PeerNode.CONNECTION_ONGOING, null, true);
    }

    public interface PresenseListener {
        public void entryAdded(PeerEntry entry);
        public void entryRemoved(PeerEntry entry);
    }
}
